﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;  //串口调试用
using System.Threading; //线程


namespace FHtool
{

    /// <summary>
    /// 串口操作封装类仅支持一个端口
    /// </summary>
    class Serial
    {
        public static SerialPort serialPort = new SerialPort(); //仅一个串口实例
        private static bool isAlreadyInit = false; //串口收发相关资源初始化

        //private StringBuilder Rev_builder = new StringBuilder(); //接收字符串，避免反复创建字符串
        //private StringBuilder Send_builder = new StringBuilder();//发送字符串
        public static long received_count = 0; //接收计数  
        public static long send_count = 0;     //发送计数

        private static Queue<byte[]> SerialSendQueue;    // 串口发送命令队列
        private static List<byte> SerialRevList;         // 串口接收数据集合
        private static ManualResetEvent SerialSendWaiter;// 串口发送线程启动信号
        private static ManualResetEvent SerialRevWaiter; // 串口接收数据处理线程启动信号


        // 串口接收数据应用回调
        public delegate void SpRecvEventHandler(params byte[] data); //定义一个委托
        public static event SpRecvEventHandler SpRecvHandler; //定义事件

        public Serial()
        {
            if (serialPort == null)
            {
                serialPort = new SerialPort();
            }
            if (!isAlreadyInit)
            {
                isAlreadyInit = true;
                //定义串口DataReceived事件，当串口接受到数据后触发事件
                serialPort.DataReceived += Sp_DataReceived; //添加事件注册

                serialPort.ReadTimeout = -1; //设置超市读取时间
                serialPort.NewLine = "/r/n"; //根据实际情况吧，当使用ReadLine()时需要定义一下
                //serialPort.RtsEnable = true; //启用RTS发送请求信号，根据实际情况吧

                // 串口收发多线程支持
                SerialSendQueue = new Queue<byte[]>();//创建串口发送队列
                SerialRevList = new List<byte>();//创建串口接受数据list
                SerialSendWaiter = new ManualResetEvent(false);
                SerialRevWaiter = new ManualResetEvent(false);
                ThreadPool.SetMaxThreads(1000, 1000);
                ThreadPool.QueueUserWorkItem(new WaitCallback(SerialSend));//串口命令发送线程，使用线程池开销较小
                ThreadPool.QueueUserWorkItem(new WaitCallback(SerialRev)); //串口数据处理线程
            }
        }

        public Serial(string portName) : this()
        {
            serialPort.PortName = portName;
        }

        public Serial(string portName, int baudRate) : this()
        {
            serialPort.PortName = portName;
            serialPort.BaudRate = baudRate; //波特率
        }
        public Serial(string portName, int baudRate, Parity parity) : this()
        {
            serialPort.PortName = portName;
            serialPort.BaudRate = baudRate; //波特率
            serialPort.Parity = parity;     //校验位
        }
        public Serial(string portName, int baudRate, Parity parity, int dataBits) : this()
        {
            serialPort.PortName = portName;
            serialPort.BaudRate = baudRate; //波特率
            serialPort.Parity = parity;     //校验位
            serialPort.DataBits = dataBits; //数据位
        }
        public Serial(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits) : this()
        {
            serialPort.PortName = portName;
            serialPort.BaudRate = baudRate; //波特率
            serialPort.Parity = parity;     //校验位
            serialPort.DataBits = dataBits; //数据位
            serialPort.StopBits = stopBits; //停止位
        }

        #region 类提供接口：打开串口、关闭串口、写数据
        /// <summary>
        /// 打开串口
        /// </summary>
        public static bool OpenPort()
        {
            if (serialPort != null)
            {
                try
                {
                    if (!serialPort.IsOpen)
                    {
                        serialPort.Open();
                        SerialSendQueue.Clear(); //发送命令队列清空
                        SerialRevList.Clear();   //接收数据清空
                        SerialSendWaiter.Set();  //启动串口发送线程
                        SerialRevWaiter.Set();   //启动串口接收线程
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message, "串口无效或已被占用！", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return false;
                }
            }
            return true;
        }

        /// <summary>
        /// 关闭串口
        /// </summary>
        public static bool ClosePort()
        {
            try
            {
                if (serialPort.IsOpen)
                {
                    serialPort.Close();
                    SerialSendQueue.Clear();  //发送命令队列清空
                    SerialRevList.Clear();    //接收数据清空
                    SerialSendWaiter.Reset(); //停止串口发送线程
                    SerialRevWaiter.Reset();  //停止串口接收线程
                }
                    
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "串口关闭失败！", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            return true;
        }

        public static void Write(string strBuf)
        {
            if (serialPort.IsOpen)
            {
                Monitor.Enter(SerialSendQueue);
                SerialSendQueue.Enqueue(Encoding.Default.GetBytes(strBuf));
                Monitor.Exit(SerialSendQueue);
            }
        }

        public static void Write(params byte[] data)
        {
            if (serialPort.IsOpen)
            {
                Monitor.Enter(SerialSendQueue);
                SerialSendQueue.Enqueue(data);
                Monitor.Exit(SerialSendQueue);
            } 
        }

        #endregion




        /// <summary>
        /// 串口接受事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            try
            {
                if (serialPort.BytesToRead > 0)
                {
                    byte[] buffer = new byte[serialPort.BytesToRead];
                    serialPort.Read(buffer, 0, buffer.Length);
                    received_count += buffer.Length;//增加接收计数

                    //serialPort.DiscardInBuffer();//丢弃接受缓冲区数据

                    Monitor.Enter(SerialRevList);
                    SerialRevList.AddRange(buffer); //交由接收处理线程处理
                    Monitor.Exit(SerialRevList);
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// 串口接受缓存数据处理线程函数
        /// </summary>
        /// <param name="obj"></param>
        private void SerialRev(object obj)
        {
            while (true)
            {
                try
                {
                    byte[] data = null;
                    SerialRevWaiter.WaitOne();
                    Monitor.Enter(SerialRevList);
                    if (SerialRevList.Count > 0) //接收缓存有数据
                    {
                        data = SerialRevList.ToArray();
                        SerialRevList.Clear();
                    }
                    Monitor.Exit(SerialRevList);

                    if (data != null)
                    {
                        SpRecvHandler(data);
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message, "串口接受线程处理错误！", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
                Thread.Sleep(10);
            }
        }

        /// <summary>
        /// 这个线程函数负责发送串口命令
        /// </summary>
        /// <param name="obj"></param>
        private void SerialSend(object obj)
        {
            while (true)
            {
                try
                {
                    SerialSendWaiter.WaitOne();
                    Monitor.Enter(SerialSendQueue); //队列排他锁，实现同步访问
                    byte[] buf = null;
                    if (SerialSendQueue.Count > 0)  //有命令
                    {
                        buf = SerialSendQueue.Dequeue(); //取出发送队列第一个命令
                    }
                    if (buf != null)
                    {
                        serialPort.Write(buf, 0, buf.Length); //发送数据
                        send_count += buf.Length;   //发送计数
                        
                        // TODO:发送回调
                    }
                    Monitor.Exit(SerialSendQueue); //释放锁
                    Thread.Sleep(10);
                }
                catch (Exception ex)
                {
                    throw ex;
                }
            }
        }
    }
}
